#include <errno.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <stddef.h>
#include <unistd.h>
#include <string.h>

#include "Epoll.h"
#include "Errors.h"

jint Java_my_epoll_Native_epollCreate0(JNIEnv* env, jclass clazz) {
	jint efd;

    // 在Linux2.6.27之前等同于：
    //      efd = epoll_create(126);
    //      fcntl(efd, F_SETFD, FD_CLOEXEC);
    // 关于设置FD_CLOEXEC的解释：
    // It sets the close-on-exec flag for the file descriptor, which causes the file descriptor to be
    // automatically (and atomically) closed when any of the exec-family functions succeed.
	efd = epoll_create1(EPOLL_CLOEXEC);
    if (efd < 0) {
        int err = errno;
        unix_errors_throwChannelExceptionErrorNo(env, "epoll_create() failed: ", err);
        return efd;
    }
    return efd;
}

jint Java_my_epoll_Native_timerCreate0(JNIEnv* env, jclass clazz) {
	jint tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
	if (tfd < 0) {
		unix_errors_throwChannelExceptionErrorNo(env, "timerfd_create() failed: ", errno);
	}
	return tfd;
}

jint Java_my_epoll_Native_eventCreate0(JNIEnv* env, jclass clazz) {
    jint eventFD = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);

    if (eventFD < 0) {
        unix_errors_throwChannelExceptionErrorNo(env, "eventfd() failed: ", errno);
    }
    return eventFD;
}

void Java_my_epoll_Native_eventFdWrite(JNIEnv* env, jclass clazz, jint fd, jlong value) {
    jint eventFD = eventfd_write(fd, (eventfd_t) value);

    if (eventFD < 0) {
        unix_errors_throwChannelExceptionErrorNo(env, "eventfd_write() failed: ", errno);
    }
}

void Java_my_epoll_Native_eventFdRead(JNIEnv* env, jclass clazz, jint fd) {
    uint64_t eventfd_t;

    if (eventfd_read(fd, &eventfd_t) != 0) {
        // something is serious wrong
        unix_errors_throwRuntimeException(env, "eventfd_read() failed");
    }
}

inline jint epollCtl(JNIEnv* env, jint efd, int op, jint fd, jint flags) {
    uint32_t events = flags;
    struct epoll_event ev = {
        .data.fd = fd,
        .events = events
    };

    return epoll_ctl(efd, op, fd, &ev);
}

jint Java_my_epoll_Native_epollCtlAdd0(JNIEnv* env, jclass clazz, jint efd, jint fd, jint flags) {
	int res = epollCtl(env, efd, EPOLL_CTL_ADD, fd, flags);
    if (res < 0) {
        return -errno;
    }
    return res;
}

jint Java_my_epoll_Native_epollCtlMod0(JNIEnv* env, jclass clazz, jint efd, jint fd, jint flags) {
    int res = epollCtl(env, efd, EPOLL_CTL_MOD, fd, flags);
    if (res < 0) {
        return -errno;
    }
    return res;
}

jint Java_my_epoll_Native_epollCtlDel0(JNIEnv* env, jclass clazz, jint efd, jint fd) {
    struct epoll_event event = { 0 };
    int res = epoll_ctl(efd, EPOLL_CTL_DEL, fd, &event);
    if (res < 0) {
        return -errno;
    }
    return res;
}

jint Java_my_epoll_Native_epollWait0(JNIEnv* env, jclass clazz, jint efd, jlong address, jint len, jint timerfd, jint sec, jint ns) {
    struct epoll_event *ev = (struct epoll_event*) (intptr_t) address;
    int res, err;

	if (timerfd > 0) {
		struct itimerspec ts;
		memset(&ts.it_interval, 0, sizeof(struct timespec));
		ts.it_value.tv_sec = sec;
		ts.it_value.tv_nsec = ns;
		if (timerfd_settime(timerfd, 0, &ts, NULL) < 0) {
			unix_errors_throwChannelExceptionErrorNo(env, "timerfd_settime() failed: ", errno);
			return -1;
		}
	}
    do {
        // 0立即返回，-1阻塞
        res = epoll_wait(efd, ev, len, -1);
        if (res > 0) {
			if (res == 1 && ev[0].data.fd == timerfd) {
				uint64_t timerFireCount;
				res = read(timerfd, &timerFireCount, sizeof(uint64_t));
				return 0;
			}
            return res;
        }
    } while((err = errno) == EINTR);
    return -err;
}

jint Java_my_epoll_Native_epollet(JNIEnv* env, jclass clazz) {
    return EPOLLET;
}

jint Java_my_epoll_Native_epollin(JNIEnv* env, jclass clazz) {
    return EPOLLIN;
}

jint Java_my_epoll_Native_epollout(JNIEnv* env, jclass clazz) {
    return EPOLLOUT;
}

jint Java_my_epoll_Native_epollrdhup(JNIEnv* env, jclass clazz) {
    return EPOLLRDHUP;
}

jint Java_my_epoll_Native_epollerr(JNIEnv* env, jclass clazz) {
    return EPOLLERR;
}

jint Java_my_epoll_Native_sizeofEpollEvent(JNIEnv* env, jclass clazz) {
    return sizeof(struct epoll_event);
}

jint Java_my_epoll_Native_offsetofEpollData(JNIEnv* env, jclass clazz) {
    return offsetof(struct epoll_event, data);
}

jlong Java_my_epoll_Native_memoryAddress(JNIEnv* env, jclass clazz, jobject buffer) {
    return (jlong) (*env)->GetDirectBufferAddress(env, buffer);
}
